package org.lightcommons.orm.hibernate;

import java.io.Serializable;
import java.util.List;
import java.util.Map;

import org.hibernate.Criteria;
import org.hibernate.HibernateException;
import org.hibernate.Query;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.Transaction;
import org.hibernate.criterion.Criterion;
import org.hibernate.criterion.DetachedCriteria;
import org.hibernate.criterion.Example;
import org.hibernate.criterion.MatchMode;
import org.hibernate.criterion.Order;
import org.hibernate.criterion.Projections;
import org.hibernate.util.SerializationHelper;
import org.lightcommons.logger.Log;
import org.lightcommons.logger.LogFactory;
import org.lightcommons.orm.DaoTemplate;
import org.lightcommons.orm.Page;

/**
 * <pre>
 *   //you can use any session holder
 *   SessionHolder sessionHolder = new ThreadLocalSessionHolder(sessionFactory);//...
 *   HibernateDaoTemplate dt = new HibernateDaoTemplate(sessionHolder);
 * </pre>
 * @author gl
 * @since Jun 15, 2008
 */
public class HibernateDaoTemplate implements DaoTemplate {
	private static final Log log = LogFactory
			.getLog(HibernateDaoTemplate.class);
 
	private final SessionHolder sessionHolder;
	
	/**
	 * @param sessionHolder
	 */
	public HibernateDaoTemplate(SessionHolder sessionHolder) {
		super();
		this.sessionHolder = sessionHolder;
	}

	/**
	 * 
	 */
	public HibernateDaoTemplate(SessionFactory sessionFactory) {
		this(new ThreadLocalSessionHolder(sessionFactory));
	}
	
	public <T> T delete(final T entity) {
		return execute(new HibernateCallback<T>(){

			public T doInHibernate(Session session) {
				session.delete(entity);
				return entity;
			}

		});
	}

	public <T> T deleteById(Class<T> entityClass, Serializable id) {
		T t = get(entityClass, id);
		return delete(t);
	}

	public <T> List<T> findAll(Class<T> entityClass) {
		return findByCriteria(DetachedCriteria.forClass(entityClass));
	}

	public <T> List<T> findByExample(T exampleEntity) {
		return findByCriteria(getExampleCriteria(exampleEntity));
	}
	
	public <T> T get(final Class<T> entityClass, final Serializable id) {
		return execute(new HibernateCallback<T>(){

			public T doInHibernate(Session session) {
				return (T) session.get(entityClass, id);
			}
			
		});
	}

	public <T> Page<T> getPage(Class<T> entityClass, int pageIndex,
			int maxResults) {
		return getPageByCriteria(DetachedCriteria.forClass(entityClass), pageIndex, maxResults);
	}

	public <T> Page<T> getPage(Class<T> entityClass, int pageIndex,
			int maxResults, String orderBy, boolean desc) {
		DetachedCriteria criteria =  DetachedCriteria.forClass(entityClass);
		if(desc)
			criteria.addOrder(Order.desc(orderBy));
		else
			criteria.addOrder(Order.asc(orderBy));
		return getPageByCriteria(DetachedCriteria.forClass(entityClass), pageIndex, maxResults);
	}
	
	public <T> Page<T> getPageByExample(T exampleEntity, int pageIndex,
			int maxResults) {
		return getPageByCriteria(getExampleCriteria(exampleEntity), pageIndex, maxResults);
	}

	public Serializable save(final Object entity) {
		return execute(new HibernateCallback<Serializable>(){

			public Serializable doInHibernate(Session session) {
				return session.save(entity);
			}
			
		});
	}

	public void saveOrUpdate(final Object entity) {
		execute(new HibernateCallback(){

			public Object doInHibernate(Session session) {
				session.saveOrUpdate(entity);
				return null;
			}
			
		});		
	}

	public void update(final Object entity) {
		execute(new HibernateCallback(){

			public Object doInHibernate(Session session) {
				session.update(entity);
				return null;
			}
			
		});
		
	}
	

	/**
	 * 执行一个回调方法
	 * @param callback
	 * @return
	 */
	public <T> T execute(HibernateCallback<T> callback){
		Session sess = sessionHolder.getSession();
		
		Transaction tx = null;
		T ret = null;
		try {
		    tx = sess.beginTransaction();
		    ret = callback.doInHibernate(sess);
		    tx.commit();
		}
		catch (RuntimeException e) {
		    if (tx != null) tx.rollback();
		    log.error(e);
		    throw e; // or display error message
		}
		return ret;
	}

	public List findByCriteria(DetachedCriteria criteria) {
		return findByCriteria(criteria, 0, -1);
	}
	
	public List findByCriteria(final DetachedCriteria criteria, final int startIndex, final int maxResults) {
		return (List) execute(new HibernateCallback(){

			public Object doInHibernate(Session session) {
				DetachedCriteria dc2 = (DetachedCriteria) SerializationHelper.clone(criteria); 
				Criteria executableCriteria = dc2.getExecutableCriteria(session);
				if (startIndex >= 0) {
					executableCriteria.setFirstResult(startIndex);
				}
				if (maxResults > 0) {
					executableCriteria.setMaxResults(maxResults);
				}
				return executableCriteria.list();
			}
			
		});
	}

	public List findByQuery(String query, Object... values) {
		return findByQuery(-1, -1, query, values);
	}

	public List findByQuery(final int startIndex,final int maxResult,final String query, final Object... values) {
		return (List) execute(new HibernateCallback(){

			public Object doInHibernate(Session session) {
				Query queryObject = session.createQuery(query);
				if (values != null) {
					for (int i = 0; i < values.length; i++) {
						queryObject.setParameter(i, values[i]);
					}
				}
				if(startIndex>=0)
					queryObject.setFirstResult(startIndex);
				if(maxResult > 0)
					queryObject.setMaxResults(maxResult);
				return queryObject.list();
			}
			
		});
	}

	public <T>List<T> findByNamedQuery(String namedQuery,Map<String,Object> values){
		return findByNamedQuery(0, -1, namedQuery, values);
	}
	
	public List findByNamedQuery(final int startIndex,final int maxResult,final String namedQuery, final Map<String,?> values){
		//TODO: findByNamedQuery
		throw new UnsupportedOperationException("not implement yet");
	}
	
	public <T> List<T> findByKeyWord(Class<T> entityClass,
			String inculdKeywords, String excludeKeywords) {
		//TODO: findByKeyWord
		throw new UnsupportedOperationException("not implement yet");
	}
	
	protected DetachedCriteria getExampleCriteria(Object exampleEntity){
		DetachedCriteria criteria = DetachedCriteria.forClass(exampleEntity.getClass());
		criteria.add(getExampleCriterion(exampleEntity));
		return criteria;
	}
	
	protected Criterion getExampleCriterion(Object exampleEntity){
		return Example.create(exampleEntity).enableLike(MatchMode.ANYWHERE).excludeNone().excludeZeroes();
	}
	
	public Page getPageByCriteria(DetachedCriteria criteria,int pageIndex,
			int maxResults){
		Page p = new Page(pageIndex,maxResults);
		List items = findByCriteria(criteria, p.getStartIndex(), maxResults);
		int total = getCountOfCriteria(criteria);
		p.setItems(items);
		p.setTotal(total);
		return p;
	}
	
	public int getCountOfCriteria(final DetachedCriteria criteria) {
		Integer count = (Integer) execute(
				new HibernateCallback() {
					public Object doInHibernate(Session session)
							throws HibernateException {
						DetachedCriteria dc2 = (DetachedCriteria) SerializationHelper.clone(criteria);
						Criteria crit = dc2.getExecutableCriteria(session);
						return crit.setProjection(Projections.rowCount())
								.uniqueResult();
						
					}
				});
		return count==null?0:count.intValue();
	}

	public <T> Page<T> getPageByQuery(String query, String countQuery,int pageIndex,
			int maxResults, Object... values) {
		Page<T> page = new Page<T>(pageIndex,maxResults);
		List<T> objects = findByQuery(page.getStartIndex(), maxResults, query, values);
		page.setItems(objects);
		Number count = (Number) findByQuery(countQuery, values).get(0);
		page.setTotal(count.intValue());
		return page;
	}
	
	public <T> Page<T> getPageByNamedQuery(String namedQuery,String countQuery, int pageIndex,
			int maxResults, Map<String, Object> values) {
		Page<T> page = new Page<T>(pageIndex,maxResults);
		List<T> objects = findByNamedQuery(page.getStartIndex(), maxResults, namedQuery, values);
		page.setItems(objects);
		Number count = (Number) findByNamedQuery(namedQuery, values).get(0);
		page.setTotal(count.intValue());
		return page;
	}
	
	public <T> Page<T> getPageByKeyWord(Class<T> entityClass,
			String inculdKeywords, String excludeKeywords, int pageIndex,
			int maxResults) {
		// TODO Auto-generated method stub
		throw new UnsupportedOperationException("get Page By Keyword is not support yet");
	}
}
